﻿using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Uiap.Infrastructure.Configurations;

namespace Uiap.Infrastructure.Runtime.Caching.Redis
{
    /// <summary>
    /// Redis缓存管理器
    /// </summary>
    public class RedisCacheManager : ICacheManager
    {
        #region Fields

        private readonly ICacheManager _perRequestCacheManager;
        private readonly IRedisConnectionWrapper _connectionWrapper;
        private readonly IDatabase _db;

        #endregion

        #region Ctor

        public RedisCacheManager(ICacheManager perRequestCacheManager,
            IRedisConnectionWrapper connectionWrapper,
            RedisConnectConfig config)
        {
            if (string.IsNullOrEmpty(config.ConnectionString))
                throw new Exception("Redis连接不能为空");

            _perRequestCacheManager = perRequestCacheManager;

            _connectionWrapper = connectionWrapper;

            _db = _connectionWrapper.GetDatabase((int)RedisDatabaseNumber.Cache);
        }

        #endregion

        #region Utilities

        /// <summary>
        /// 根据前获取缓存列表
        /// </summary>
        /// <param name="endPoint">网络地址</param>
        /// <param name="prefix">前缀</param>
        /// <returns></returns>
        protected virtual IEnumerable<RedisKey> GetKeys(EndPoint endPoint, string prefix = null)
        {
            var server = _connectionWrapper.GetServer(endPoint);

            //推荐使用以下注释代码，需要配置管理员权限 ",allowAdmin=true"
            //server.FlushDatabase();

            var keys = server.Keys(_db.Database, string.IsNullOrEmpty(prefix) ? null : $"{prefix}*");

            return keys;
        }

        /// <summary>
        /// 根据key获取缓存
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <param name="key">Key</param>
        /// <returns></returns>
        protected virtual async Task<T> GetAsync<T>(string key)
        {
            //在httpcontext上下文缓存一下，优化性能，减少redis的连接读取
            if (_perRequestCacheManager.IsSet(key))
                return _perRequestCacheManager.Get(key, 0, () => default(T));

            //从redis获取
            var serializedItem = await _db.StringGetAsync(key);
            if (!serializedItem.HasValue)
                return default(T);

            var item = JsonConvert.DeserializeObject<T>(serializedItem);
            if (item == null)
                return default(T);

            //向当前httpcontext添加缓存
            _perRequestCacheManager.Set(key, item, 0);

            return item;
        }

        /// <summary>
        /// 添加缓存
        /// </summary>
        /// <param name="key">缓存key</param>
        /// <param name="data">缓存值</param>
        /// <param name="cacheTime">缓存时间(秒)</param>
        protected virtual async Task SetAsync(string key, object data, int cacheTime)
        {
            if (data == null)
                return;

            //设置缓存有效时间
            var expiresIn = TimeSpan.FromSeconds(cacheTime);

            var serializedItem = JsonConvert.SerializeObject(data);

            //添加到redis
            await _db.StringSetAsync(key, serializedItem, expiresIn);
        }

        /// <summary>
        /// 标识某个key是否已经设置了缓存
        /// </summary>
        /// <param name="key">Key</param>
        /// <returns></returns>
        protected virtual async Task<bool> IsSetAsync(string key)
        {
            //在httpcontext上下文缓存一下，优化性能，减少redis的连接读取
            if (_perRequestCacheManager.IsSet(key))
                return true;

            return await _db.KeyExistsAsync(key);
        }

        #endregion

        #region Methods

        /// <summary>
        /// 根据key获取缓存，如果缓存不存在，则调用方法获取并设置缓存
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <param name="key">缓存key</param>
        /// <param name="acquire">缓存不存在时调用的方法</param>
        /// <param name="cacheTime">缓存时间（秒），如果不传则使用配置的默认过期时间</param>
        /// <returns></returns>
        public async Task<T> GetAsync<T>(string key, Func<Task<T>> acquire, int? cacheTime = null)
        {
            //如果已经存在，直接返回
            if (await IsSetAsync(key))
                return await GetAsync<T>(key);

            //调用方法获取
            var result = await acquire();

            //设置缓存
            if ((cacheTime ?? AppConstString.RedisDefaultCacheTime) > 0)
                await SetAsync(key, result, cacheTime ?? AppConstString.RedisDefaultCacheTime);

            return result;
        }

        /// <summary>
        /// 根据key获取缓存，如果缓存不存在，则调用方法获取并设置缓存
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <param name="key">缓存key</param>
        /// <returns></returns>
        public virtual T Get<T>(string key)
        {
            //在httpcontext上下文缓存一下，优化性能，减少redis的连接读取
            if (_perRequestCacheManager.IsSet(key))
                return _perRequestCacheManager.Get(key, 0, () => default(T));

            //从redis获取
            var serializedItem = _db.StringGet(key);
            if (!serializedItem.HasValue)
                return default(T);
            var item = JsonConvert.DeserializeObject<T>(serializedItem);
            if (item == null)
                return default(T);

            //设置httpcontext缓存
            _perRequestCacheManager.Set(key, item, 0);

            return item;
        }

        /// <summary>
        /// 根据key获取缓存，如果缓存不存在，则调用方法获取并设置缓存
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <param name="key">缓存key</param>
        /// <param name="acquire">缓存不存在时调用的方法</param>
        /// <param name="cacheTime">缓存时间（秒），如果不传则使用配置的默认过期时间</param>
        /// <returns></returns>
        public virtual T Get<T>(string key, Func<T> acquire, int? cacheTime = null)
        {
            //缓存中存在，直接返回
            if (IsSet(key))
                return Get<T>(key);

            //不存在则调用获取方法
            var result = acquire();

            //添加到缓存
            if ((cacheTime ?? AppConstString.RedisDefaultCacheTime) > 0)
                Set(key, result, cacheTime ?? AppConstString.RedisDefaultCacheTime);

            return result;
        }

        /// <summary>
        /// 添加到缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <param name="data">缓存值</param>
        /// <param name="cacheTime">缓存时间（秒）</param>
        public virtual void Set(string key, object data, int cacheTime)
        {
            if (data == null)
                return;

            //设置过期时间
            var expiresIn = TimeSpan.FromSeconds(cacheTime);
            var serializedItem = JsonConvert.SerializeObject(data);

            //添加到redis
            _db.StringSet(key, serializedItem, expiresIn);
        }

        /// <summary>
        /// 标识指定的key是否已经存在
        /// </summary>
        /// <param name="key">缓存key</param>
        /// <returns>/returns>
        public virtual bool IsSet(string key)
        {
            //在httpcontext上下文缓存一下，优化性能，减少redis的连接读取
            if (_perRequestCacheManager.IsSet(key))
                return true;

            return _db.KeyExists(key);
        }

        /// <summary>
        /// 根据key移除缓存
        /// </summary>
        /// <param name="key">Key</param>
        public virtual void Remove(string key)
        {
            _db.KeyDelete(key);
            _perRequestCacheManager.Remove(key);
        }


        /// <summary>
        /// 根据key前缀移除缓存
        /// </summary>
        /// <param name="prefix">前缀</param>
        public virtual void RemoveByPrefix(string prefix)
        {
            _perRequestCacheManager.RemoveByPrefix(prefix);

            foreach (var endPoint in _connectionWrapper.GetEndPoints())
            {
                var keys = GetKeys(endPoint, prefix);

                _db.KeyDelete(keys.ToArray());
            }
        }

        /// <summary>
        /// 清除所有缓存数据
        /// </summary>
        public virtual void Clear()
        {
            foreach (var endPoint in _connectionWrapper.GetEndPoints())
            {
                var keys = GetKeys(endPoint).ToArray();

                //不要使用 _perRequestCacheManager.Clear(),
                //有可能缓存了其他服务器对象
                foreach (var redisKey in keys)
                {
                    _perRequestCacheManager.Remove(redisKey.ToString());
                }

                _db.KeyDelete(keys);
            }
        }

        /// <summary>
        /// 释放资源
        /// </summary>
        public virtual void Dispose()
        {
            //if (_connectionWrapper != null)
            //    _connectionWrapper.Dispose();
        }

        #endregion
    }
}
